#pragma once

#include "fwd.h"
#include "path.h"
#include <string>
#include <memory>


namespace evm { namespace fs {

class file_status {
public:
    explicit file_status(file_type ft = file_type::none, perms prms = perms::unknown) noexcept
            : type_(ft), perms_(prms) { }

    file_status(const file_status &) noexcept = default;

    file_status(file_status &&) noexcept = default;

    ~file_status() = default;

    file_status &operator=(const file_status &) noexcept = default;

    file_status &operator=(file_status &&) noexcept = default;

    file_type type() const noexcept { return type_; }

    perms permissions() const noexcept { return perms_; }

    void type(file_type ft) noexcept { type_ = ft; }

    void permissions(perms prms) noexcept { perms_ = prms; }

private:
    file_type type_;
    perms perms_;
};

using std::string;

class directory_entry {
public:
    directory_entry() = default;
    directory_entry(const directory_entry&) = default;
    directory_entry(directory_entry&&) noexcept = default;
    explicit directory_entry(const string& p) : path_(p) { }
    explicit directory_entry(const fs::path& p) : path_(p) { }
    ~directory_entry() = default;

    directory_entry& operator=(const directory_entry&) = default;
    directory_entry& operator=(directory_entry&&) = default;

    void assign(const string& p) { path_ = p; }
    void assign(const fs::path& p) { path_ = p; }

    void replace_filename(const fs::path& p) {
        path_ = fs::path(path_).parent_path() / p;
    }

    void replace_filename(const string& p) {
        path_ = fs::path(path_).parent_path() / p;
    }

    const string& str() const noexcept { return path_; }
    operator const string&() const noexcept { return path_; }

    fs::path path() const noexcept { return path_; }
    operator fs::path() const noexcept { return path_; }

    file_status status() const { return fs::status(path_); }

    file_status symlink_status() const { return fs::symlink_status(path_); }

    bool operator==(const directory_entry& rhs) const noexcept { return path_ == rhs.path_; }

    bool operator!=(const directory_entry& rhs) const noexcept { return path_ != rhs.path_; }

    bool operator<(const directory_entry& rhs) const noexcept { return path_ < rhs.path_; }
    
    bool operator<=(const directory_entry& rhs) const noexcept { return path_ <= rhs.path_; }

    bool operator>(const directory_entry& rhs) const noexcept { return path_ > rhs.path_; }

    bool operator>=(const directory_entry& rhs) const noexcept { return path_ >= rhs.path_; }

private:
    std::string path_;
};

struct _Dir;
class recursive_directory_iterator;

class directory_iterator {
public:
    typedef directory_entry value_type;
    typedef ptrdiff_t difference_type;
    typedef const directory_entry* pointer;
    typedef const directory_entry& reference;
    typedef std::input_iterator_tag iterator_category;

    directory_iterator() noexcept = default;

    explicit directory_iterator(const path& p, directory_options options = directory_options::none,
                                error_code* ec = nullptr);

    explicit directory_iterator(const path& p, error_code& ec)
            : directory_iterator(p, directory_options::none, &ec) {}

    directory_iterator(const directory_iterator&) = default;

    directory_iterator(directory_iterator&&) noexcept = default;

    ~directory_iterator() = default;

    directory_iterator& operator=(const directory_iterator& rhs) = default;
    directory_iterator& operator=(directory_iterator&& rhs) noexcept = default;

    const directory_entry& operator*() const;
    const directory_entry* operator->() const { return &**this; }
    
    directory_iterator& operator++();

    directory_iterator& increment(error_code &ec) noexcept;

    directory_iterator operator++(int) {
        auto tmp = *this;
        ++*this;
        return tmp;
    }

private:
    friend bool operator==(const directory_iterator& lhs, const directory_iterator& rhs);

    friend class recursive_directory_iterator;

    std::shared_ptr<_Dir> dir_;
};

inline directory_iterator begin(directory_iterator iter) { return iter; }

inline directory_iterator end(directory_iterator) { return directory_iterator(); }

inline bool operator==(const directory_iterator& lhs, const directory_iterator& rhs) {
    return !rhs.dir_.owner_before(lhs.dir_) && !lhs.dir_.owner_before(rhs.dir_);
}

inline bool operator!=(const directory_iterator& lhs, const directory_iterator& rhs) {
    return !(lhs == rhs);
}

class recursive_directory_iterator
{
public:
    typedef directory_entry        value_type;
    typedef ptrdiff_t              difference_type;
    typedef const directory_entry* pointer;
    typedef const directory_entry& reference;
    typedef std::input_iterator_tag     iterator_category;

    recursive_directory_iterator() noexcept = default;

    explicit recursive_directory_iterator(const path& p, directory_options options = directory_options::none,
                                          error_code* ec = nullptr);

    recursive_directory_iterator(const recursive_directory_iterator&) = default;

    recursive_directory_iterator(recursive_directory_iterator&&) noexcept = default;

    ~recursive_directory_iterator();

    directory_options options() const { return options_; }
    int depth() const;
    bool recursion_pending() const { return pending_; }

    const directory_entry& operator*() const;
    const directory_entry* operator->() const { return &**this; }

    recursive_directory_iterator& operator=(const recursive_directory_iterator&) noexcept;
    recursive_directory_iterator& operator=(recursive_directory_iterator&&) noexcept;

    recursive_directory_iterator& operator++();
    recursive_directory_iterator& increment(error_code &ec) noexcept;

    recursive_directory_iterator operator++(int) {
        auto tmp = *this;
        ++*this;
        return tmp;
    }

    void pop();

    void disable_recursion_pending() { pending_ = false; }

private:
    friend bool operator==(const recursive_directory_iterator& lhs, const recursive_directory_iterator& rhs);

    struct _Dir_stack;
    std::shared_ptr<_Dir_stack> dirs_;
    directory_options options_;
    bool pending_;
};

inline recursive_directory_iterator begin(recursive_directory_iterator iter) { return iter; }

inline recursive_directory_iterator end(recursive_directory_iterator) { return recursive_directory_iterator(); }

inline bool operator==(const recursive_directory_iterator& lhs, const recursive_directory_iterator& rhs) {
    return !rhs.dirs_.owner_before(lhs.dirs_) && !lhs.dirs_.owner_before(rhs.dirs_);
}

inline bool operator!=(const recursive_directory_iterator& lhs, const recursive_directory_iterator& rhs) {
    return !(lhs == rhs);
}

}}
